#include <stdlib.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"
#include "driver/ledc.h"
#include "esp_log.h"
#include "esp_check.h"
#include "esp_lcd_backlight.h"
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(4, 3, 0)
#include "soc/ledc_periph.h" // to invert LEDC output on IDF version < v4.3
#endif

#define LEDC_MODE       LEDC_LOW_SPEED_MODE

typedef struct {
    int index;        // Either GPIO or LEDC channel
} esp_lcd_backlight_t;

static const char* TAG = "bcklght";

esp_err_t esp_lcd_backlight_create(const esp_lcd_backlight_config_t* config, esp_lcd_backlight_handle_t* bckl_handle)
{
    esp_err_t ret = ESP_OK;

    ESP_GOTO_ON_FALSE(
        config,
        ESP_ERR_INVALID_ARG,
        err,
        TAG,
        "invalid argument"
    );

    ESP_GOTO_ON_FALSE(
        GPIO_IS_VALID_OUTPUT_GPIO(config->gpio_num),
        ESP_ERR_INVALID_ARG,
        err,
        TAG,
        "Invalid GPIO number"
    );

    esp_lcd_backlight_t* bckl_dev = malloc(sizeof(esp_lcd_backlight_t));
    ESP_GOTO_ON_FALSE(bckl_dev, ESP_ERR_NO_MEM, err, TAG, "no mem for lcd backlight driver");

#if CONFIG_LCD_BACKLIGHT_DRIVER_PWM == 1
    bckl_dev->index = config->channel_idx;
    
    const ledc_timer_config_t backlight_timer = {
        .speed_mode = LEDC_MODE,
        .duty_resolution  = LEDC_TIMER_13_BIT,
        .timer_num = config->timer_idx,
        .freq_hz = 5000,
        .clk_cfg = LEDC_AUTO_CLK
    };
    
    const ledc_channel_config_t backlight_channel = {
        .gpio_num = config->gpio_num,
        .speed_mode = LEDC_MODE,
        .channel = config->channel_idx,
        .intr_type = LEDC_INTR_DISABLE,
        .timer_sel = config->timer_idx,
        .duty = 0,
        .hpoint = 0
    };

    ESP_ERROR_CHECK(ledc_timer_config(&backlight_timer));
    ESP_ERROR_CHECK(ledc_channel_config(&backlight_channel));

#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(4, 3, 0)   
    gpio_matrix_out(config->gpio_num, ledc_periph_signal[LEDC_MODE].sig_out0_idx + config->channel_idx, config->output_invert, 0);
#endif

#elif CONFIG_LCD_BACKLIGHT_DRIVER_DISCRET == 1
    bckl_dev->index = config->gpio_num;
    gpio_pad_select_gpio(config->gpio_num);
    ESP_ERROR_CHECK(gpio_set_direction(config->gpio_num, GPIO_MODE_OUTPUT));
    gpio_matrix_out(config->gpio_num, SIG_GPIO_OUT_IDX, config->output_invert, false);
#endif

    *bckl_handle = bckl_dev;

    ret = ESP_OK;

err:
    return ret;
}

esp_err_t esp_lcd_backlight_set(esp_lcd_backlight_handle_t bckl_handle, uint8_t brightness_percent)
{
    esp_err_t ret;

    ESP_GOTO_ON_FALSE(bckl_handle, ESP_ERR_INVALID_ARG, err, TAG, "invalid arg");
    ESP_GOTO_ON_FALSE(brightness_percent <= 100, ESP_ERR_INVALID_ARG, err, TAG, "invalid arg");

    const esp_lcd_backlight_t* bckl_dev = (const esp_lcd_backlight_t*) bckl_handle;
    ESP_LOGI(TAG, "Setting LCD backlight: %d%%", brightness_percent);

#if CONFIG_LCD_BACKLIGHT_DRIVER_PWM == 1
    uint32_t duty_cycle = (0x2000 * brightness_percent) / 100; // LEDC resolution set to 13bits, thus: 100% = 8192
    ESP_ERROR_CHECK(ledc_set_duty(LEDC_MODE, bckl_dev->index, duty_cycle));
    ESP_ERROR_CHECK(ledc_update_duty(LEDC_MODE, bckl_dev->index));
#elif CONFIG_LCD_BACKLIGHT_DRIVER_DISCRET == 1
    ESP_ERROR_CHECK(gpio_set_level(bckl_dev->index, brightness_percent));
#endif

    return ESP_OK;

err:
    return ret;
}

esp_err_t esp_lcd_backlight_delete(esp_lcd_backlight_handle_t bckl_handle)
{
    esp_err_t ret;

    ESP_GOTO_ON_FALSE(bckl_handle, ESP_ERR_INVALID_ARG, err, TAG, "invalid arg");

    const esp_lcd_backlight_t* bckl_dev = (const esp_lcd_backlight_t*) bckl_handle;

#if CONFIG_LCD_BACKLIGHT_DRIVER_PWM == 1
    ledc_stop(LEDC_MODE, bckl_dev->index, 0);
#elif CONFIG_LCD_BACKLIGHT_DRIVER_DISCRET == 1
    gpio_reset_pin(bckl_dev->index);
#endif
    
    free (bckl_handle);
    
    return ESP_OK;

err:
    return ret;
}